<?php

namespace backend\components;

use \Yii;
use yii\base\Component;
use yii\base\ExitException;
use yii\web\ForbiddenHttpException;
use yii\web\ServerErrorHttpException;

class AdminAuth extends Component
{
    /**
     * 默认排除的路由表达式
     *
     * @var array
     */
    public $excludeRoute        = [
        '^debug/.*/.*',
        '^gii/.*/.*',
        '^/guest/.*',
        '^admin/default/error$',
        '^user/guest/(login|logout)$',
    ];

    /**
     * @var string 权限配置文件
     */
    public $genAuthItemFilePath = '@backend/config/rbac/items.php';

    public $autoGenerateSuperAdminRoleName          = 'super-admin';
    public $autoGenerateDefaultAdminRoleName        = 'default-admin';
    public $autoGenerateSuperAdminRoleDescription   = 'SuperAdmin';
    public $autoGenerateDefaultAdminRoleDescription = 'DefaultAdmin';

    public $accessBackendPermission      = 'accessBackend';
    public $accessBackendIndexPermission = 'controller:admin/default';

    public function getGeneratePermissionArray()
    {
        $config = [
            'defaultRoles'   => [
                'user',
            ],
            'itemFile'       => '@app/config/rbac/items.php',
            'assignmentFile' => '@app/config/rbac/assignments.php',
            'ruleFile'       => '@app/config/rbac/rules.php',
        ];
        $auth = new RbacPhpManager($config);

        $this->refreshPermission($auth);

        $generatePermissions = array_keys($auth->loadGenerateDataFromFile());
        return $generatePermissions;
    }

    /**
     * RBAC检查接入权限，建议绑定到App的请求事件之前
     *
     * @param \yii\base\ActionEvent $event
     *
     * 方法会根据当前访问的路由组装Permission名称，格式为：module-name/controller-name/action-name
     * 然后与RBAC中的授权进行匹配
     *
     * 在Module Bootstrap中引入：
     * Yii::$app->on(Application::EVENT_BEFORE_ACTION, function ($event) {
     *   $adminAuth = Yii::$app->get('adminAuthComponent');
     *   $adminAuth->adminAccessCheck($event);
     *   });
     *
     * @throws \yii\base\ExitException
     * @throws \yii\web\ForbiddenHttpException
     */
    public function adminAccessCheck($event)
    {
        $moduleName = Yii::$app->id == $event->action->controller->module->id ? "/" : $event->action->controller->module->id . '/';
        $route = $moduleName . $event->action->controller->id . '/' . $event->action->id;
        if ($this->excludeRoute)
        {
            foreach ($this->excludeRoute as $exRoute)
            {
                if (preg_match("@{$exRoute}@", $route))
                {
                    Yii::info('当前路由是例外路由, 跳过权限校验', __METHOD__);
                    return;
                }
            }
        }
        $actionPermission = "route:" . $route;

        $profileToken = "AdminAuth检查权限: " . $actionPermission;
        Yii::beginProfile($profileToken, __METHOD__);

        $isAllowAccess = false;
        if ( ! $isAllowAccess)
        {
            //Yii::trace('检查用户授权:' . Yii::$app->user->identity->getId(), __METHOD__);
            $isAllowAccess = Yii::$app->user->can($actionPermission);
        }
        Yii::endProfile($profileToken, __METHOD__);

        if ( ! $isAllowAccess)
        {
            Yii::info($actionPermission . '授权没通过', __METHOD__);
            if (Yii::$app->user->isGuest)
            {
                Yii::$app->user->loginRequired();
                throw new ExitException();
            }
            else
            {
                throw new ForbiddenHttpException("you have no right to access permission " . $actionPermission);
            }
        }
    }

    /**
     * 扫描路由产生permission
     *
     * @param mixed $auth
     * @return bool
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\web\ServerErrorHttpException
     */
    public function refreshPermission(&$auth = null)
    {
        $appMetaDataComponent = Yii::$app->get('appMetaData');
        $routeData = $appMetaDataComponent->getRoutes();
        $routeMap = $routeData['map'];
        $routeExtra = $routeData['extra'];

        /**
         * @var \yii\rbac\PhpManager @auth
         */
        $auth = ! $auth ? Yii::$app->authManager : $auth;
        $auth->setGenerateMode();
        $auth->removeAllPermissions();

        if (empty($this->autoGenerateSuperAdminRoleName) || empty($this->autoGenerateSuperAdminRoleDescription))
        {
            throw new ServerErrorHttpException("default super admin role name or description is empty.");
        }

        //创建超级用户
        $superAdminRole = $auth->createRole($this->autoGenerateSuperAdminRoleName);
        $superAdminRole->description = $this->autoGenerateSuperAdminRoleDescription;

        $this->savePermission($superAdminRole, $auth);
        $auth->removeChildren($superAdminRole);

        foreach ($routeMap as $moduleName => $controllerActions)
        {

            if (is_array($controllerActions))
            {

                $modulePermissionName = 'module:' . $moduleName;
                $modulePermissionObject = $auth->createPermission($modulePermissionName);
                $modulePermissionObject->description =
                    $routeExtra[$moduleName]['description'] ?
                        "【" . $routeExtra[$moduleName]['description'] . "】" :
                        "【" . $modulePermissionName . "】";
                $this->savePermission($modulePermissionObject, $auth);
                $auth->removeChildren($modulePermissionObject);

                foreach ($controllerActions as $controllerName => $actions)
                {

                    if (is_array($actions))
                    {

                        $controllerPermissionName = 'controller:' . $moduleName . '/' . $controllerName;
                        $controllerPermissionObject = $auth->createPermission($controllerPermissionName);
                        $controllerPermissionObject->description =
                            $routeExtra[$moduleName]['controllers'][$controllerName]['description'] ?
                                "【" . $routeExtra[$moduleName]['controllers'][$controllerName]['description'] . "】" :
                                "【" . $controllerPermissionName . "】";;
                        $this->savePermission($controllerPermissionObject, $auth);
                        $auth->removeChildren($controllerPermissionObject);
                        foreach ($actions as $actionName)
                        {

                            $actionPermissionName = 'route:' . $moduleName . '/' . $controllerName . '/' . $actionName;
                            $controllerChildren[] = $actionPermissionName;
                            $permissionObject = $auth->createPermission($actionPermissionName);
                            $permissionObject->description =
                                $routeExtra[$moduleName]['controllers'][$controllerName]['actions'][$actionName]['description'] ?
                                    $routeExtra[$moduleName]['controllers'][$controllerName]['actions'][$actionName]['description'] : $actionPermissionName;
                            $this->savePermission($permissionObject, $auth);

                            $auth->addChild($controllerPermissionObject, $permissionObject);
                        }

                        $auth->addChild($modulePermissionObject, $controllerPermissionObject);
                    }

                }

                $auth->addChild($superAdminRole, $modulePermissionObject);

            }

        }

        //创建accessBackend节点
        $accessBackendPermissionObject = $auth->createPermission($this->accessBackendPermission);
        $accessBackendPermissionObject->description = $this->accessBackendPermission;
        $this->savePermission($accessBackendPermissionObject, $auth);

        $auth->addChild($superAdminRole, $accessBackendPermissionObject);
        $this->savePermission($superAdminRole, $auth);

        //创建普通用户
        $defaultAdminRole = $auth->createRole($this->autoGenerateDefaultAdminRoleName);
        $defaultAdminRole->description = $this->autoGenerateDefaultAdminRoleDescription;

        $this->savePermission($defaultAdminRole, $auth);
        $auth->removeChildren($defaultAdminRole);


        $auth->addChild($defaultAdminRole, $accessBackendPermissionObject);

        $defaultIndexPermission = $auth->getItem($this->accessBackendIndexPermission);
        if ($defaultIndexPermission)
        {
            $auth->addChild($defaultAdminRole, $defaultIndexPermission);
        }
        $this->savePermission($defaultAdminRole, $auth);

        return true;
    }

    /**
     * 保存生成的授权
     *
     * @param      $permissionObject
     * @param null $auth
     */
    protected function savePermission($permissionObject, $auth = null)
    {
        $auth = $auth === null ? Yii::$app->authManager : $auth;
        if (null == $auth->getPermission($permissionObject->name))
        {
            $auth->add($permissionObject);
        }
        else
        {
            $auth->update($permissionObject->name, $permissionObject);
        }
    }
}